import md5 from 'js-md5'

/**
 * 微信支付签名计算工具类
 * 专门用于前端计算微信支付签名
 * 注意：生产环境建议在后端计算签名以保证安全性
 */
export class WxPaySignUtil {
  
  /**
   * 生成随机字符串
   * @param {number} length - 字符串长度，默认32位
   * @returns {string} 随机字符串
   */
  static generateNonceStr(length = 32) {
    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
    let result = ''
    for (let i = 0; i < length; i++) {
      result += chars.charAt(Math.floor(Math.random() * chars.length))
    }
    return result
  }

  /**
   * 生成时间戳
   * @returns {string} 10位时间戳字符串
   */
  static generateTimeStamp() {
    return Math.floor(Date.now() / 1000).toString()
  }

  /**
   * 生成订单号
   * @param {string} prefix - 订单号前缀，默认为空
   * @returns {string} 订单号
   */
  static generateOrderNo(prefix = '') {
    const date = new Date()
    const year = date.getFullYear()
    const month = String(date.getMonth() + 1).padStart(2, '0')
    const day = String(date.getDate()).padStart(2, '0')
    const hour = String(date.getHours()).padStart(2, '0')
    const minute = String(date.getMinutes()).padStart(2, '0')
    const second = String(date.getSeconds()).padStart(2, '0')
    const millisecond = String(date.getMilliseconds()).padStart(3, '0')
    const random = Math.floor(Math.random() * 1000).toString().padStart(3, '0')
    
    return `${prefix}${year}${month}${day}${hour}${minute}${second}${millisecond}${random}`
  }

  /**
   * 计算微信支付签名
   * @param {Object} params - 参与签名的参数对象
   * @param {string} apiKey - 微信支付API密钥
   * @param {string} signType - 签名类型，默认MD5
   * @returns {Object} 包含签名和签名字符串的对象
   */
  static calculateSign(params, apiKey, signType = 'MD5') {
    try {
      // 1. 参数排序（排除sign字段和空值）
      const keys = Object.keys(params)
        .filter(key => {
          const value = params[key]
          return value !== undefined && 
                 value !== null && 
                 value !== '' && 
                 key !== 'sign'
        })
        .sort()

      // 2. 拼接字符串
      let stringA = ''
      keys.forEach((key, index) => {
        if (index > 0) stringA += '&'
        stringA += `${key}=${params[key]}`
      })

      // 3. 拼接API密钥
      const stringSignTemp = `${stringA}&key=${apiKey}`

      // 4. 计算签名
      let sign = ''
      if (signType.toUpperCase() === 'MD5') {
        sign = md5(stringSignTemp).toUpperCase()
      } else {
        throw new Error(`不支持的签名类型: ${signType}`)
      }

      return {
        sign,
        signString: stringSignTemp,
        sortedParams: keys.map(key => `${key}=${params[key]}`).join('&')
      }
    } catch (error) {
      console.error('签名计算错误:', error)
      throw new Error(`签名计算失败: ${error.message}`)
    }
  }

  /**
   * 验证签名
   * @param {Object} params - 包含签名的参数对象
   * @param {string} apiKey - 微信支付API密钥
   * @param {string} signType - 签名类型
   * @returns {boolean} 签名是否正确
   */
  static verifySign(params, apiKey, signType = 'MD5') {
    try {
      const originalSign = params.sign
      if (!originalSign) {
        return false
      }

      const { sign: calculatedSign } = this.calculateSign(params, apiKey, signType)
      return originalSign === calculatedSign
    } catch (error) {
      console.error('签名验证错误:', error)
      return false
    }
  }

  /**
   * 准备统一下单签名参数
   * @param {Object} config - 微信支付基本配置
   * @param {Object} orderInfo - 订单信息
   * @returns {Object} 包含签名的统一下单参数
   */
  static prepareUnifiedOrderSign(config, orderInfo) {
    const {
      appid,
      mch_id,
      key,
      notify_url
    } = config

    const {
      body,
      out_trade_no,
      total_fee,
      spbill_create_ip = '127.0.0.1',
      trade_type = 'APP',
      openid // JSAPI支付需要
    } = orderInfo

    // 参数验证
    if (!total_fee || total_fee <= 0) {
      throw new Error('支付金额必须大于0')
    }
    
    // 确保金额为整数(分)
    const totalFeeInt = parseInt(total_fee)
    if (totalFeeInt !== Number(total_fee)) {
      console.warn(`金额 ${total_fee} 已转换为整数 ${totalFeeInt} (单位：分)`)
    }

    // 构建统一下单参数
    const unifiedOrderParams = {
      appid,
      mch_id,
      nonce_str: this.generateNonceStr(),
      body,
      out_trade_no,
      total_fee: totalFeeInt, // 确保为整数
      spbill_create_ip,
      notify_url,
      trade_type
    }

    // 如果是JSAPI支付，需要添加openid
    if (trade_type === 'JSAPI' && openid) {
      unifiedOrderParams.openid = openid
    }

    // 计算签名
    const signResult = this.calculateSign(unifiedOrderParams, key)
    unifiedOrderParams.sign = signResult.sign

    return {
      params: unifiedOrderParams,
      signInfo: signResult
    }
  }

  /**
   * 准备App支付签名参数
   * @param {Object} config - 微信支付基本配置
   * @param {string} prepayId - 预支付交易会话标识
   * @returns {Object} 包含签名的App支付参数
   */
  static prepareAppPaySign(config, prepayId) {
    const { appid, mch_id, key } = config
    
    const timeStamp = this.generateTimeStamp()
    const nonceStr = this.generateNonceStr()
    const packageValue = 'Sign=WXPay'
    
    const appPayParams = {
      appid,
      partnerid: mch_id,
      prepayid: prepayId,
      package: packageValue,
      noncestr: nonceStr,
      timestamp: timeStamp
    }

    // 计算签名
    const signResult = this.calculateSign(appPayParams, key)
    appPayParams.sign = signResult.sign

    return {
      params: appPayParams,
      signInfo: signResult
    }
  }

  /**
   * 准备JSAPI支付签名参数（小程序/公众号）
   * @param {Object} config - 微信支付基本配置
   * @param {string} prepayId - 预支付交易会话标识
   * @returns {Object} 包含签名的JSAPI支付参数
   */
  static prepareJSAPIPaySign(config, prepayId) {
    const { appid, key } = config
    
    const timeStamp = this.generateTimeStamp()
    const nonceStr = this.generateNonceStr()
    const packageValue = `prepay_id=${prepayId}`
    const signType = 'MD5'
    
    const jsapiPayParams = {
      appId: appid,
      timeStamp,
      nonceStr,
      package: packageValue,
      signType
    }

    // 计算签名
    const signResult = this.calculateSign(jsapiPayParams, key)
    jsapiPayParams.paySign = signResult.sign

    return {
      params: jsapiPayParams,
      signInfo: signResult
    }
  }

  /**
   * 准备退款签名参数
   * @param {Object} config - 微信支付基本配置
   * @param {Object} refundInfo - 退款信息
   * @returns {Object} 包含签名的退款参数
   */
  static prepareRefundSign(config, refundInfo) {
    const { appid, mch_id, key } = config
    const {
      out_trade_no,
      out_refund_no,
      total_fee,
      refund_fee,
      refund_desc = '退款'
    } = refundInfo

    const refundParams = {
      appid,
      mch_id,
      nonce_str: this.generateNonceStr(),
      out_trade_no,
      out_refund_no,
      total_fee: parseInt(total_fee),
      refund_fee: parseInt(refund_fee),
      refund_desc
    }

    // 计算签名
    const signResult = this.calculateSign(refundParams, key)
    refundParams.sign = signResult.sign

    return {
      params: refundParams,
      signInfo: signResult
    }
  }

  /**
   * 准备查询订单签名参数
   * @param {Object} config - 微信支付基本配置
   * @param {string} outTradeNo - 商户订单号
   * @returns {Object} 包含签名的查询参数
   */
  static prepareQueryOrderSign(config, outTradeNo) {
    const { appid, mch_id, key } = config

    const queryParams = {
      appid,
      mch_id,
      out_trade_no: outTradeNo,
      nonce_str: this.generateNonceStr()
    }

    // 计算签名
    const signResult = this.calculateSign(queryParams, key)
    queryParams.sign = signResult.sign

    return {
      params: queryParams,
      signInfo: signResult
    }
  }

  /**
   * 格式化签名调试信息
   * @param {Object} signInfo - 签名信息对象
   * @returns {string} 格式化的调试信息
   */
  static formatSignDebugInfo(signInfo) {
    return `签名计算过程:
1. 参数排序: ${signInfo.sortedParams}
2. 拼接密钥: ${signInfo.signString}
3. MD5加密: ${signInfo.sign}
4. 转换大写: ${signInfo.sign}`
  }

  /**
   * 批量生成测试数据
   * @param {number} count - 生成数量
   * @returns {Array} 测试数据数组
   */
  static generateTestData(count = 5) {
    const testData = []
    for (let i = 0; i < count; i++) {
      testData.push({
        orderNo: this.generateOrderNo('TEST'),
        nonceStr: this.generateNonceStr(),
        timeStamp: this.generateTimeStamp(),
        amount: Math.floor(Math.random() * 10000) + 1
      })
    }
    return testData
  }
}

// 默认导出
export default WxPaySignUtil 